mod utils;

mod buffer;

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use wasm_bindgen::prelude::*;

pub use buffer::*;

#[wasm_bindgen]
extern "C" {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, maptalks-markercluter-core!");
}

#[derive(Serialize, Deserialize)]
pub struct InputPoint {
    pub x: f64,
    pub y: f64,
    pub text_sum_property: f64,
    pub id: u32,
}

#[derive(Clone, Default)]
struct Cluster {
    sum_x: f64,
    sum_y: f64,
    count: u32,
    text_sum_property: f64,
    children_ids: Vec<u32>,
    key: String,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct ClusterResult {
    pub center_x: f64,
    pub center_y: f64,
    pub count: u32,
    pub text_sum_property: f64,
    pub children_ids: Vec<u32>,
    pub key: String,
}

#[derive(Serialize)]
struct FinalOutput {
    clusters: HashMap<String, ClusterResult>,
    #[serde(rename = "clusterMap")]
    cluster_map: HashMap<String, ClusterResult>,
}

impl From<Cluster> for ClusterResult {
    fn from(value: Cluster) -> Self {
        ClusterResult {
            center_x: value.sum_x / value.count as f64,
            center_y: value.sum_y / value.count as f64,
            count: value.count,
            text_sum_property: value.text_sum_property,
            children_ids: value.children_ids,
            key: value.key,
        }
    }
}

#[wasm_bindgen]
pub fn compute_cluster_grid(
    points_js: JsValue,
    min_x: f64,
    min_y: f64,
    resolution: f64,
    max_cluster_radius: f64,
) -> Result<JsValue, JsValue> {
    std::panic::set_hook(Box::new(console_error_panic_hook::hook));
    let points: Vec<InputPoint> = serde_wasm_bindgen::from_value(points_js)?;
    let r = resolution * max_cluster_radius;
    if r == 0.0 {
        let output = FinalOutput {
            clusters: HashMap::new(),
            cluster_map: HashMap::new(),
        };
        return Ok(serde_wasm_bindgen::to_value(&output)?);
    }
    let mut grids: HashMap<String, Cluster> = HashMap::new();
    for point in points {
        let gx = (point.x - min_x).floor() as i64;
        let gy = (point.y - min_y).floor() as i64;
        let key = format!("{},{}", gx, gy);
        let cluster = grids.entry(key.clone()).or_insert_with(|| Cluster {
            key: key.clone(),
            ..Default::default()
        });
        cluster.sum_x += point.x;
        cluster.sum_y += point.y;
        cluster.count += 1;
        cluster.text_sum_property += point.text_sum_property;
        cluster.children_ids.push(point.id);
    }
    let merge_r = r / 2.0;
    let mut merged_grids: HashMap<String, Cluster> = HashMap::new();
    let mut cluster_map_mapping: HashMap<String, String> = HashMap::new();
    let mut visited: HashMap<String, bool> = HashMap::new();
    let keys: Vec<String> = grids.keys().cloned().collect();
    for key in keys {
        if visited.contains_key(&key) {
            continue;
        }
        let c1 = grids.get(&key).unwrap().clone();
        let gx_gy = key
            .split(",")
            .map(|s| s.parse::<i64>().unwrap())
            .collect::<Vec<_>>();
        let gx = gx_gy[0];
        let gy = gx_gy[1];
        let mut merged_cluster = c1.clone();
        visited.insert(key.clone(), true);
        cluster_map_mapping.insert(key.clone(), key.clone());
        let c1_center_x = c1.sum_x / c1.count as f64;
        let c1_center_y = c1.sum_y / c1.count as f64;
        for ii in -1..1 {
            for jj in -1..1 {
                if ii == 0 && jj == 0 {
                    continue;
                }
                let neighbor_key = format!("{}_{}", gx + ii, gy + jj);
                if let Some(c2) = grids.get(&neighbor_key) {
                    if visited.contains_key(&neighbor_key) {
                        continue;
                    }
                    let c2_center_x = c2.sum_x / c2.count as f64;
                    let c2_center_y = c2.sum_y / c2.count as f64;
                    let dist_x = c1_center_x - c2_center_x;
                    let dist_y = c1_center_y - c2_center_y;
                    let dist = (dist_x * dist_x + dist_y * dist_y).sqrt();
                    if dist <= merge_r {
                        merged_cluster.sum_x += c2.sum_x;
                        merged_cluster.sum_y += c2.sum_y;
                        merged_cluster.count += c2.count;
                        merged_cluster.text_sum_property += c2.text_sum_property;
                        merged_cluster
                            .children_ids
                            .extend_from_slice(&c2.children_ids);
                        visited.insert(neighbor_key.clone(), true);
                        cluster_map_mapping.insert(neighbor_key.clone(), key.clone());
                    }
                }
            }
        }
        merged_grids.insert(key.clone(), merged_cluster);
    }
    let mut js_clusters = HashMap::new();
    let mut js_cluster_map = HashMap::new();
    for (key, cluster) in merged_grids {
        let result: ClusterResult = cluster.clone().into();
        js_clusters.insert(key.clone(), result.clone());
        js_cluster_map.insert(key.clone(), result.clone());
    }
    for (original_key, merged_key) in cluster_map_mapping {
        if original_key != merged_key {
            if let Some(merged_result) = js_clusters.get(&merged_key) {
                js_cluster_map.insert(original_key, merged_result.clone());
            }
        }
    }
    let output = FinalOutput {
        clusters: js_clusters,
        cluster_map: js_cluster_map,
    };
    Ok(serde_wasm_bindgen::to_value(&output)?)
}
